package com.ekang.wxpay.util;

import com.google.common.base.Joiner;
import org.apache.commons.codec.digest.DigestUtils;
import org.jdom.input.SAXBuilder;
import org.xml.sax.InputSource;

import javax.crypto.Cipher;
import java.io.*;
import java.lang.reflect.Method;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

public class RSAUtil {


    public static byte[] decrypt(byte[] encryptedBytes, PrivateKey privateKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception {
        int keyByteSize = keyLength / 8;
        int decryptBlockSize = keyByteSize - reserveSize;
        int nBlock = encryptedBytes.length / keyByteSize;
        ByteArrayOutputStream outbuf = null;
        try {
            Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            outbuf = new ByteArrayOutputStream(nBlock * decryptBlockSize);
            for (int offset = 0; offset < encryptedBytes.length; offset += keyByteSize) {
                int inputLen = encryptedBytes.length - offset;
                if (inputLen > keyByteSize) {
                    inputLen = keyByteSize;
                }
                byte[] decryptedBlock = cipher.doFinal(encryptedBytes, offset, inputLen);
                outbuf.write(decryptedBlock);
            }
            outbuf.flush();
            return outbuf.toByteArray();
        } catch (Exception e) {
            throw new Exception("DEENCRYPT ERROR:", e);
        } finally {
            try {
                if (outbuf != null) {
                    outbuf.close();
                }
            } catch (Exception e) {
                outbuf = null;
                throw new Exception("CLOSE ByteArrayOutputStream ERROR:", e);
            }
        }
    }

    public static byte[] encrypt(byte[] plainBytes, PublicKey publicKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception {
        int keyByteSize = keyLength / 8;
        int encryptBlockSize = keyByteSize - reserveSize;
        int nBlock = plainBytes.length / encryptBlockSize;
        if ((plainBytes.length % encryptBlockSize) != 0) {
            nBlock += 1;
        }
        ByteArrayOutputStream outbuf = null;
        try {
            Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);

            outbuf = new ByteArrayOutputStream(nBlock * keyByteSize);
            for (int offset = 0; offset < plainBytes.length; offset += encryptBlockSize) {
                int inputLen = plainBytes.length - offset;
                if (inputLen > encryptBlockSize) {
                    inputLen = encryptBlockSize;
                }
                byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
                outbuf.write(encryptedBlock);
            }
            outbuf.flush();
            return outbuf.toByteArray();
        } catch (Exception e) {
            throw new Exception("ENCRYPT ERROR:", e);
        } finally {
            try {
                if (outbuf != null) {
                    outbuf.close();
                }
            } catch (Exception e) {
                outbuf = null;
                throw new Exception("CLOSE ByteArrayOutputStream ERROR:", e);
            }
        }
    }

    public static PrivateKey getPriKey(String privateKeyPath, String keyAlgorithm) {
        PrivateKey privateKey = null;
        InputStream inputStream = null;
        try {
            if (inputStream == null) {
                System.out.println("hahhah1!");
            }

            inputStream = new FileInputStream(privateKeyPath);

            privateKey = getPrivateKey(inputStream, keyAlgorithm);

        } catch (Exception e) {
            System.out.println("加载私钥出错!");
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                    System.out.println("加载私钥,关闭流时出错!");
                }
            }
        }
        return privateKey;
    }

    public static PublicKey getPubKey(String publicKeyPath, String keyAlgorithm) {
        PublicKey publicKey = null;
        InputStream inputStream = null;
        try {


            inputStream = new FileInputStream(publicKeyPath);


            publicKey = getPublicKey(inputStream, keyAlgorithm);


        } catch (Exception e) {

            e.printStackTrace();//EAD PUBLIC KEY ERROR
            System.out.println("加载公钥出错!");
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                    System.out.println("加载公钥,关闭流时出错!");
                }
            }
        }
        return publicKey;
    }

    public static PublicKey getPublicKey(InputStream inputStream, String keyAlgorithm) throws Exception {
        try {

            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));

            StringBuilder sb = new StringBuilder();
            String readLine = null;

            while ((readLine = br.readLine()) != null) {
                if (readLine.charAt(0) == '-') {
                    continue;
                } else {
                    sb.append(readLine);
                    sb.append('\r');
                }
            }

            //String ss="MIIBCgKCAQEA9pn2S5M1XEzblrVWx5AQ7Jkx7a/x6anjq19HuMDThpuyke455MfYcIUjiI8AtAGMCZ7Ox8uIGzHFnZ4w7v5bGcu8iQHL3C4XDgSVCTW48lHPqd/IMd2OGruMeTobGXz1jpibqz5wuPKRRxRtf/PiL+JfdMjivR+riot/44fBFb8GYB7Qj8FHns42KkJvuv95oZuWAcPOXCecrTZ8pYQvfDRw0QywGP1g7kTPrXj/Go+zPMvlI2NRqrzh7pm/Uh+MkD2+bpidfElv2boo8ZPx1fBVcZoMTkkasHzL29DYh7ABs9DSJhMNBREgeTnCbj+Elrug7/g0Jik1aNXiK9IhTQIDAQAB";

            X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(decodeBase64(sb.toString()));

            KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);

            //下行出错  java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: DerInputStream.getLength(): lengthTag=127, too big.
            PublicKey publicKey = keyFactory.generatePublic(pubX509);

            return publicKey;
        } catch (Exception e) {
            e.printStackTrace();

            throw new Exception("READ PUBLIC KEY ERROR:", e);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                inputStream = null;
                throw new Exception("INPUT STREAM CLOSE ERROR:", e);
            }
        }
    }

    public static PrivateKey getPrivateKey(InputStream inputStream, String keyAlgorithm) throws Exception {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder sb = new StringBuilder();
            String readLine = null;
            while ((readLine = br.readLine()) != null) {
                if (readLine.charAt(0) == '-') {
                    continue;
                } else {
                    sb.append(readLine);
                    sb.append('\r');
                }
            }

            PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(decodeBase64(sb.toString()));

            KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);

            PrivateKey privateKey = keyFactory.generatePrivate(priPKCS8);

            return privateKey;
        } catch (Exception e) {
            throw new Exception("READ PRIVATE KEY ERROR:", e);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                inputStream = null;
                throw new Exception("INPUT STREAM CLOSE ERROR:", e);
            }
        }
    }

    //一下面是base64的编码和解码
    public static String encodeBase64(byte[] input) throws Exception {
        Class clazz = Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
        Method mainMethod = clazz.getMethod("encode", byte[].class);
        mainMethod.setAccessible(true);
        Object retObj = mainMethod.invoke(null, new Object[]{input});
        return (String) retObj;
    }

    /***
     * decode by Base64
     */
    public static byte[] decodeBase64(String input) throws Exception {
        Class clazz = Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
        Method mainMethod = clazz.getMethod("decode", String.class);
        mainMethod.setAccessible(true);
        Object retObj = mainMethod.invoke(null, input);
        return (byte[]) retObj;
    }

    /**
     * 获取一定长度的随机字符串
     *
     * @param length 指定字符串长度
     * @return 一定长度的字符串
     */
    public static String getRandomStringByLength(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    /**
     * MD5编码
     *
     * @param origin 原始字符串
     * @return 经过MD5加密之后的结果
     */
    public static String md5Hex(String origin) {
        return DigestUtils.md5Hex(origin);
    }

    public static String sign(Map<String, Object> map, String key) {
        String str = Joiner.on("&").withKeyValueSeparator("=").join(map);
        str += "&key=" + key;
        return md5Hex(str).toUpperCase();
    }


    /*
     * 将SortedMap<Object,Object> 集合转化成 xml格式
     */
    public static String getRequestXml(SortedMap<String, Object> parameters) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
                sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
            } else {
                sb.append("<" + k + ">" + v + "</" + k + ">");
            }
        }
        sb.append("</xml>");
        return sb.toString();
    }


    public static String Progress_resultParseXml(String xml) {
        String publicKey = null;
        try {
            StringReader read = new StringReader(xml);

            InputSource source = new InputSource(read);

            SAXBuilder sb = new SAXBuilder();

            org.jdom.Document doc;
            doc = (org.jdom.Document) sb.build(source);

            org.jdom.Element root = doc.getRootElement();
            List<org.jdom.Element> list = root.getChildren();

            if (list != null && list.size() > 0) {
                for (org.jdom.Element element : list) {
                    if ("pub_key".equals(element.getName())) {
                        publicKey = element.getText();
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return publicKey;
    }


    public static void getPublicKey(String mch_id, String key, String certPath) throws Exception {
        //1.0 拼凑所需要传递的参数 map集合
        SortedMap<String, Object> parameters = new TreeMap<String, Object>();
        String nonce_str = getRandomStringByLength(32);
        parameters.put("mch_id", mch_id);
        parameters.put("nonce_str", nonce_str);
        parameters.put("sign_type", "MD5");
        //2.0 根据要传递的参数生成自己的签名~~注意creatSign是自己封装的一个类。大家可以在下面自主下载
        String sign = sign(parameters, key);
        System.out.println(sign);
        //3.0 把签名放到map集合中【因为签名也要传递过去】
        parameters.put("sign", sign);
        //4.0将当前的map结合转化成xml格式

        String reuqestXml = getRequestXml(parameters);
        //5.0 发送请求到微信请求公钥Api。发送请求是一个方法来的~~注意需要带着证书哦

        FileInputStream inputStream = new FileInputStream(new File(certPath));
        String url = "https://fraud.mch.weixin.qq.com/risk/getpublickey";

        String response = WeixinUtil.postXmlWithKey(url, reuqestXml, inputStream, mch_id);

        //6.0 解析返回的xml数据===》map集合
        String publicKey = Progress_resultParseXml(response);
        System.out.println(publicKey);
    }


    public static void main(String[] args) throws Exception {
       //把PKCS#1 转 PKCS#8:  openssl rsa -RSAPublicKey_in -in pcs1.pem -out  pcs8.pem


      // RSAUtil.getPublicKey("1489325322", "6WL1jQDvtPyKfBfWtAO32qEQ3XeGyjIW", "./cert/apiclient_cert.p12");

        //需要被加密的字符串
        String encBankAcctName = "小郑"; //加密的银行账户名
        //定义自己公钥的路径
        String keyfile = "./cert/pcs8.pem"; //
        //RSA工具类提供了，根据加载PKCS8密钥文件的方法
        PublicKey pub = RSAUtil.getPubKey(keyfile, "RSA");
        //rsa是微信付款到银行卡要求我们填充的字符串
        String rsa = "RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING";
        // 进行加密
        byte[] estr = RSAUtil.encrypt(encBankAcctName.getBytes(), pub, 2048, 11, rsa);   //对银行账号进行base加密
        encBankAcctName = Base64.encode(estr);//并转为base64格式
        //测试输出
        System.out.println(encBankAcctName);
    }


}
